package bjut.zrpc.core.optimization.proctol;

import bjut.zrpc.core.optimization.constant.ZRpcConstant;
import bjut.zrpc.core.optimization.enums.ZRpcCompressEnum;
import bjut.zrpc.core.optimization.enums.ZRpcSerializerEnum;
import bjut.zrpc.core.optimization.extension.ExtensionLoader;
import bjut.zrpc.core.optimization.interfaces.Compress;
import bjut.zrpc.core.optimization.interfaces.Serializer;
import bjut.zrpc.core.optimization.message.ZRpcMessage;
import bjut.zrpc.core.optimization.message.ZRpcRequestMessage;
import bjut.zrpc.core.optimization.message.ZRpcResponseMessage;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;


import java.util.Arrays;

/**
 * @Author: zhuzhiming
 * @Date: 2022/6/24 0:51
 */
public class MessageDecoder extends LengthFieldBasedFrameDecoder {

    public MessageDecoder() {
        this(ZRpcConstant.MAX_LENGTH, 5, 4, -9, 0);
    }

    public MessageDecoder(int maxFrameLength, int lengthFieldOffset, int lengthFieldLength, int lengthAdjustment, int initialBytesToStrip) {
        super(maxFrameLength, lengthFieldOffset, lengthFieldLength, lengthAdjustment, initialBytesToStrip);
    }


    @Override
    protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception {

        Object decode = super.decode(ctx, in);

        if (decode instanceof ByteBuf) {

            ByteBuf frame = (ByteBuf) decode;

            if (frame.readableBytes() > ZRpcConstant.HEAD_LENGTH) {
                try {
                    return decodeFrame(frame);
                } catch (Exception e) {
                    throw new RuntimeException(e.getMessage());
                } finally {
                    frame.release();
                }
            }
        }
        return decode;
    }

    private Object decodeFrame(ByteBuf frame) {

        checkMagicNumber(frame);
        checkVersion(frame);

        int fullLength = frame.readInt();
        byte messageType = frame.readByte();
        byte codec = frame.readByte();
        byte compressCode = frame.readByte();
        int requestId = frame.readInt();

        ZRpcMessage zRpcMessage = ZRpcMessage.builder()
                .messageType(messageType)
                .requestId(requestId)
                .codec(codec)
                .compress(compressCode)
                .build();

        if (messageType == ZRpcConstant.HEART_REQUEST_TYPE) {
            zRpcMessage.setBodyData(ZRpcConstant.HEART_REQUEST_DATA);
            return zRpcMessage;
        } else if (messageType == ZRpcConstant.HEART_RESPONSE_TYPE) {
            zRpcMessage.setBodyData(ZRpcConstant.HEART_RESPONSE_DATA);
            return zRpcMessage;
        }

        int bodyLength = fullLength - ZRpcConstant.HEAD_LENGTH;

        if (bodyLength > 0) {
            byte[] bodyData = new byte[bodyLength];
            frame.readBytes(bodyData);

            String compressType = ZRpcCompressEnum.getCompressName(compressCode);
            Compress compress = ExtensionLoader.getExtensionLoader(Compress.class).getExtension(compressType);
            byte[] bodyDecompress = compress.deCompress(bodyData);

            String serializationType = ZRpcSerializerEnum.getSerializationName(codec);
            Serializer serializer = ExtensionLoader.getExtensionLoader(Serializer.class).getExtension(serializationType);

            if (messageType == ZRpcConstant.REQUEST_TYPE) {
                ZRpcRequestMessage requestMessage = serializer.deSerialize(bodyDecompress, ZRpcRequestMessage.class);
                zRpcMessage.setBodyData(requestMessage);
            } else if (messageType == ZRpcConstant.RESPONSE_TYPE) {
                ZRpcResponseMessage zRpcResponseMessage = serializer.deSerialize(bodyDecompress, ZRpcResponseMessage.class);
                zRpcMessage.setBodyData(zRpcResponseMessage);
            }
        }
        return zRpcMessage;
    }

    private void checkVersion(ByteBuf frame) {
        if (ZRpcConstant.VERSION != frame.readByte()) {
            throw new RuntimeException("version not match");
        }
    }

    private void checkMagicNumber(ByteBuf frame) {

        int length = ZRpcConstant.MAGIC_NUMBER.length;
        byte[] bytes = new byte[length];
        frame.readBytes(bytes);

        for (int i = 0; i < bytes.length; i++) {
            if (bytes[i] != ZRpcConstant.MAGIC_NUMBER[i]) {
                throw new IllegalArgumentException("unKnow magic number:  " + Arrays.toString(bytes));
            }
        }


//        for (int i = 0; i < ZRpcConstant.MAGIC_NUMBER.length; i++) {
//            if (frame.readByte() != ZRpcConstant.MAGIC_NUMBER[i]) {
//                throw new RuntimeException(" magic number not match");
//            }
//        }
    }
}
